<?php
/*------------------------------------------------------------------------------
Copyright (C) 2006 J.Ducastel fredbird@fredbird.org

object-oriented, domxml, curl based xml-rpc library

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
------------------------------------------------------------------------------*/

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcServer {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    */
    function ooXmlRpcServer($iniFile=null) {
        $this->__construct($iniFile);
    }/**/

    /**
    *
    * @access
    */
    function __construct($iniFile=null) {
        //$this->deb=&wak::getservice('wakdebug');
        $this->loadConfig($iniFile);
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * Tries to serve current http xml-rpc method call
    * @access public
    * @return bool
    */
    function serveRequest() {
        //$this=&domXmlRpcServer::_getsingleton();
        // check for http authentication
        if (!$this->authenticationCheck($this->cfg['users'])) {
            // return $this->raiseFa
            return false;
        }
        // retrieve and build method call
        if (!$call=&$this->getMethodCall()) {
            // failed, fault have been raised by getMethodCall
            // echo "invalid call : ".$this->deb->vardump($call);
            return false;
        }
        // runs call
        // echo $this->deb->vardump($this);
        if (!$response=&$this->ResponseToCall($call)) {
            // failed, raise fault
            return false;
        }
        // echo $deb->vardump($cm);
        // sending response
        return $this->sendResponse($response);
    }/**/

    /**
    * build and send methodResponse including fault
    * @param int $code faultCode
    * @param string $string faultString
    * @access public
    * @return bool
    */
    function raiseFault($code,$string=null) {
        // has response already been sent ?
        if ($this->responseSent)
            return false;
        // getting predefined desc if required and available
        if (is_null($string) and isset($this->cfg['faults'][$code]))
            $string=$this->cfg['faults'][$code];
        $fault=&new ooXmlRpcFault($code,$string);
        if (!$fault->isValid()) {
            return false;
        }
        $response=&new ooXmlRpcMethodResponse();
        //$responseNode=&$response->getNode();
        //$fault->appendToNode($responseNode);
        $response->setContent($fault);
        return $this->sendResponse($response);
    }/**/



    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var array configuration data
    * 'service_classes'=> array('serviceName'=>'class')
    * 'service_files'=> array('serviceName'=>'file')
    */
    var $cfg=array(
        // http authentication config
        'auth'=>array(
            'use'=>false
            , 'pwd_file'=>'')
        , 'users'=>array()
        // classes associated to services
        , 'service_classes'=>array()
        // files defining service classes
        , 'service_files'=>array()
        // default faultstring for fault codes
        , 'faults'=>array(
            400=>'bad request'
            , 401=>'unauthorized'
            , 403=>'forbidden'
            , 404=>'not found'
            , 500=>'internal server error'
            , 501=>'not implemented'
            , 503=>'service unavailable'));
    /**
    * @var bool has methodResponse be sent ?
    */
    var $responseSent=false;

    /**
    * @var type desc
    */
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    * build response to call
    * find and execute call's implementation
    * @param ooxmlrpcmethodcall &$call
    * @access protected
    * @return ooxmlrpcmethodresponse
    */
    function & responseToCall(&$call) {
        // $deb=&wak::getservice('wakdebug');
        $response=false;
        if (!is_a($call,'ooxmlrpcmethodcall')) {
            // bad argument
            $this->raiseFault(500);
            return $response;
        }
        $method=$call->getmethod(); //echo "<p>method: $method</p>";
        $service=$call->getServiceName(); //echo "<p>service: $service</p>";
        // get params
        $params=&$call->getParams();
        // build response
        $response=&new ooXmlRpcMethodResponse();
        // is call service based ?
        if ($service) {
            // include service file
            if (isset($this->cfg['service_files'][$service])) {
                $files=explode(' ',$this->cfg['service_files'][$service]);
                foreach ($files as $file) {
                    if (!file_exists($file)) {
                        // trigger_error("ooXmlRpcServer : required file $file don't exist",E_USER_WARNING);
                        // return false;
                    }
                    include_once($file);
                }
            }
            // get service class
            if (isset($this->cfg['service_classes'][$service]))
                $class=$this->cfg['service_classes'][$service];
            else $class=$service;
            // is class defined ?
            if (!class_exists($class)) {
                // not defined, raising fault
                $str="ooXmlRpcServer : class $class undefined";
                $this->raiseFault(503,$str);
                return false;
            }
            // is method defined ?
            if (!in_array($method,get_class_methods($class))) {
                // method not implemented, raising fault
                $this->raiseFault(501);
                return false;
            }
            // run class method
            $result=eval('return '.$class.'::'.$method.'($params);');
            if (!$response->setContent($result)) {
                // invalid result, raise fault
                //echo '<p>invalid result : '.$deb->vardump($result).'</p>';
                $this->raiseFault('500','bad method result');
            }
            //$deb=&wak::getservice('wakdebug');
            //echo $deb->vardump($result);
            // echo wakaffvar($response);
        } else {
            // searching within method_functions
            // searching within own methods
            // not found
            // method not implemented, raising fault
            $this->raiseFault(501);
            return false;
        }
        return $response;
    }/**/

    /**
    * retrieves and build method call from http raw post data
    * @param
    * @access protected
    * @return domXmlRpcMethodCall|false
    */
    function & getMethodCall() {
        // echo "getting call !";
        //$this=&domXmlRpcServer::_getsingleton();
        $call=false;
        if (!$xml=&$this->getrawpost()) {
            // no data at all,  raising fault
            $this->raiseFault(400,'no xml data');
            return $call;//
        }
        // building call instance
        $call=& new ooXmlRpcMethodCall();
        if (!$call->setFromXml($xml)) {
            // failed to retrieve xml, raising fault
            $this->raiseFault(400,'invalid xml data');
            $call=false;
        }
        return $call;
    }/**/

    /**
    * returns raw post data
    * @param
    * @access protected
    * @return string|false
    */
    function getRawPost() {
        if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])
        or !$raw=$GLOBALS['HTTP_RAW_POST_DATA']) {
            // hack to retrieve raw data
            $raw=file_get_contents('php://input');
        }
        return $raw;
    }/**/

    /**
    * sends method response to client
    * @param
    * @access protected
    * @return
    */
    function sendResponse(&$response) {
        if ($this->responseSent)
            return false;
        $xml=$response->getxml();
        // sending headers
        header('Content-type: text/xml');
        // sending content
        //$deb=&wak::getservice('wakdebug');
        //echo $deb->vardump($xml,'html',5000);
        echo $xml;
        $this->responseSent=true;
        return true;
    }/**/

    /**
    * loads config from ini file, merge data into $this->cfg
    * search ini file at cfg/{serverclass}.cfg.ini
    * @param string $file
    * @access protected
    * @return bool
    */
    function loadConfig($file=null) {
        if (is_null($file))
            $file='cfg/'.get_class($this).'.cfg.ini'; //echo $file;
        if (file_exists($file) and $cfg=parse_ini_file($file,true)) {
            // merging new config with default one
            $this->cfg=array_merge_recursive($this->cfg,$cfg);
            //trigger_error("loaded config from $file");
            return true;
        }
        return false;
    }/**/

    /**
    * checks for http authentication
    * @param array users user => pwd list of authorized users with pwd
    * @access protected
    * @return bool
    */
    function authenticationCheck($users=array()) {
        // is authentication required ?
        if (!$this->cfg['auth']['use'])
            return true;
        // are login/pwd provided ?
        if (!isset($_SERVER['PHP_AUTH_USER'])
        or !isset($_SERVER['PHP_AUTH_PW'])) {
            $realm='realm';
            //header( 'WWW-Authenticate: Basic realm="'.$realm.'"' );
            header( 'WWW-Authenticate: Basic' );
            header( 'HTTP/1.0 401 Unauthorized' );
            //echo 'Authorization Required.';
            $this->raiseFault(401);
            return false;
            exit;
        } else {
            // checking for provided login/pwd
            $provided_login=$_SERVER['PHP_AUTH_USER'];
            $provided_pwd=$_SERVER['PHP_AUTH_PW'];
            // searching for user/pwd
            foreach ($users as $login => $pwd) {
                if ($login==$provided_login and $pwd==$provided_pwd)
                    return true;
            }
            // invalid user/pwd
            $this->raiseFault(401);
            return false;
        }
        
        return false;
    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcService {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    */
    function ooXmlRpcClient(&$server) {
        $this->__construct($server);
    }/**/

    /**
    *
    * @access
    */
    function __construct(&$server) {
        $this->server=&$server;
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access public
    * @return
    */
    function raiseFault($code,$string=null) {
        return $this->server->raiseFault($code,$string);
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var ooXmlRpcServer reference to server
    */
    var $server;

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/


    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcClient {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    * /
    function ooXmlRpcClient() {
        $this->__construct();
    }/**/

    /**
    *
    * @access
    * /
    function __construct() {

    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * sets url for xml-rpc calls
    * @param string $url
    * @access public
    * @return bool
    */
    function setUrl($url) {
        $this->url=$url;
        return true;
    }/**/

    /**
    * builds methodCall from method name + params
    * @param string $methodName
    * @param array optional, parameters array
    * @access public
    * @return bool
    */
    function setCall($methodName,$params=array()) {
        // searching for custom call-building method
        // fall back to standard method
        if (!$call=$this->buildCall($methodName,$params)) {
            return false;
        }
        $this->methodCall=&$call;
        //echo wakaffvar($call);
        return true;
    }/**/

    /**
    * imports pre-built methodCall
    * @param ooXmlRpcMethodCall
    * @access public
    * @return bool
    */
    function setMethodCall(& $methodCall) {
        if (!is_a($methodCall,'ooXmlRpcMethodCall'))
            return false;
        $this->methodCall=&$methodCall;
        return true;
    }/**/

    /**
    * sets http authentification data
    * @param
    * @access public
    * @return bool
    */
    function setAuth($login,$pwd) {
        if (!$login or !$pwd) {
            return false;
        }
        $this->auth=$login.':'.$pwd;
        return true;
    }/**/

    /**
    * executes call
    * @access public
    * @return bool
    */
    function issueCall() {
        // getting http request result
        if (!$this->getHttpResponse()) {
            return false;
        }
        // building methodResponse
        //$this->
        return true;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    */
    function getHttpResponse() {
        if (!$this->httpResponse) {
            if (!is_a($this->methodCall,'ooXmlRpcMethodCall')) {
                return false;
            }
            $xml=$this->methodCall->getXml(); //echo wakaffvar($xml);
            $this->httpResponse=$this->httpXmlRpcRequest($xml,$this->url);
        }
        return $this->httpResponse;
    }/**/

    /**
    * returns response as an object
    * @access public
    * @return ooXmlRpcMethodResponse
    */
    function getMethodResponse() {
        if (!$this->methodResponse) {
            $xml=$this->getHttpResponse();
            $response=new ooXmlRpcMethodResponse();
            if ($response->setFromXml($xml))
                $this->methodResponse=&$response;
        }
        return $this->methodResponse;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/


/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var string default/current url
    */
    var $url;
    /**
    * @var ooXmlRpcMethodCall call instance
    */
    var $methodCall;
    /**
    * @var string http response content
    */
    var $httpResponse;
    /**
    * @var ooXmlRpcMethodResponse response instance
    */
    var $methodResponse;
    /**
    * @var string http authentication as login:pwd
    */
    var $auth;
    /**
    * @var type desc
    */
    /**
    * @var type desc
    */
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    * Issue server xml-rpc request and return http response content
    * uses curl library
    * @param string $url xml-rpc url
    * @param string $xml complete methodCall xml
    * @access protected
    * @return string
    */
    function httpXmlRpcRequest($xml,$url=null) {
        if (is_null($url))
            $url=$this->url;
        // init curl session
        $ch =&curl_init($url);
        // setting correct xml-rpc parameters
        // POST call
        curl_setopt($ch,CURLOPT_POST,true);
        // headers
        $headers=array(
            'Content-type : text/xml'
        );
        curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
        // setting raw POST data
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        // http authentication
        if ($this->auth)
            curl_setopt($ch,CURLOPT_USERPWD,$this->auth);

        // executing http call
        // returning content rather than printing it
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
        $response=curl_exec($ch);
        $info=curl_getinfo($ch);
        //$deb=wak::getservice('wakdebug');
        // echo $deb->vardump($info);
        // echo wakaffvar($info);
        curl_close($ch);
        return $response;
    }/**/

    /**
    * builds methodCall
    * @param string $name
    * @access protected
    * @return ooXmlRpcMethodCall
    * /
    function buildCall($name,$params=array()) {

    }/**/

    /**
    * returns response to methodcall, as xml
    * @param
    * @access protected
    * @return
    */
    function & getResponseXmlToCall($methodCall,$url=null) {
        if (is_null($url))
            $url=$this->url;
        // getting xml
        $callXml=$methodCall->getXml(); //echo $callXml;
        // sending call
        $responseXml=$this->httpXmlRpcRequest($callXml,$url);
        // echo $responseXml;
        return $responseXml;
    }/**/

    /**
    * returns response to methodcall, as methodResponse
    * @param
    * @access protected
    * @return
    */
    function & getResponseToCall(& $methodCall, $url=null) {
        if (is_null($url))
            $url=$this->url;
        $xml=$this->getResponseXmlToCall($methodCall,$url);
        // echo htmlentities($responseXml);
        //echo $responseXml;
        // building response
        $response=new ooXmlRpcMethodResponse();
        $response->setFromXml($xml);
        // echo wakaffvar($response);
        return $response;
    }/**/

    /**
    * builds methodCall instance
    * @param string $methodName
    * @param array $params
    * @access protected
    * @return ooXmlRpcMethodCall
    */
    function & buildCall($methodName,$params=array()) {
        $call=new ooXmlRpcMethodCall();
        $call->setName($methodName);
        for ($n=0; $n<count($params); $n++) {
            $call->params->setParam($params[$n]);
        }
        // echo wakaffvar($call);
        return $call;
    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcDomNode {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    */
    function ooXmlRpcDomNode(&$node) {
        $this->__construct($node);
    }/**/

    /**
    *
    * @access
    */
    function __construct(&$node) {
        $this->node=&$node;
        $this->doc=&$this->node->owner_document();
        $this->xContext=&xpath_new_context($this->doc);
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * return xpath selected node(s) from domDocument
    * @param domNode $
    * @param string xpath
    * @param bool $singleResult optional, return directly first result nodes
    * @access public
    * @return array
    */
    function getXpathNodes($xpath) {
        //echo "<p>searching $xpath from </p><pre>";
        //var_dump($this->node);
        // getting results
        if (!$results=$this->xContext->xpath_eval($xpath,$this->node)
            or count($results->nodeset)<1)
            // no results
            return false;
        //return $results->nodeset;
        $nodes=array();
        for ($n=0; $n<count($results->nodeset); $n++) {
            $node=&new ooXmlRpcDomNode($results->nodeset[$n]);
            $nodes[]=&$node;
        }
        return $nodes;
    }/**/

    /**
    * return self node's content
    * @access public
    * @return mixed
    */
    function getContent() {
        return $this->node->get_content();
    }/**/

    /**
    * sets self node's content
    * @param mixed &$content
    * @access public
    * @return bool
    */
    function setContent(&$content) {
        return $this->node->set_content($content);
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    */
    function getTagName() {
        return $this->node->tagname();
    }/**/

    /**
    * return xml dump of the node
    * @param
    * @access public
    * @return string
    * /
    function getXml() {
        if (is_a($this->node,'domdocument'))
            return $this->node->dump_mem();
        return $this->node->dump_node($this->doc);
    }/**/

    /**
    * return domDocument reference
    * @access public
    * @return domDocument
    */
    function & getDomDoc() {
        return $this->doc;
    }/**/

    /**
    * build, append and return node's child
    * @param string tagName
    * @access public
    * @return & ooXmlRpcDomNode
    */
    function & makeChild($tagName) {
        // build dom node
        $dom=$this->doc->create_element($tagName);
        //append domnode
        $this->node->append_child($dom);
        $node=&new ooXmlRpcDomNode($dom);
        return $node;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var type desc
    */
    var $node;
    var $doc; // root domDocument reference
    var $xContext; // xpathcontext instance

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcMethodCall {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    */
    function ooXmlRpcMethodCall() {
        $this->__construct();
    }/**/

    /**
    *
    * @access
    */
    function __construct() {
        $this->params=&new ooXmlRpcParams();
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * sets content from xml string
    * @param string $xml
    * @access public
    * @return bool
    */
    function setFromXml($xml) {
        // creating domDocument instance
        if (!$domDocument=&domxml_open_mem($xml)) {
            return false;
        }
        // building
        $callNode=new ooXmlRpcDomNode($domDocument);
        //$deb=&wak::getservice('wakdebug');
        // setting method name
        if (!$nodes=$callNode->getxpathnodes('/methodCall/methodName/text()')) {
            return false;
        }
        $this->name=$nodes[0]->getContent();
        // getting params
        // $this->params=&new
        if ($nodes=$callNode->getxpathnodes('/methodCall/params')) {
            //echo $deb->vardump($nodes);
            //$pNode=&new ooXmlRpcDomNode($nodes[0]);
            $this->params->setFromNode($nodes[0]);
        }
        //$this->params=&new domXmlRpcParams();
        return true;
    }/**/

    /**
    * returns full methodName
    * @param
    * @access public
    * @return
    */
    function getName() {
        return $this->name;
    }/**/

    /**
    * sets methodName
    * @param string $name
    * @access public
    * @return
    */
    function setName($name) {
        if (!is_string($name))
            return false;
        $this->name=$name;
        return true;
    }/**/

    /**
    * returns method part of methodname (after the point='.')
    * @param
    * @access public
    * @return string|false
    */
    function getMethod() {
        if (!eregi('([a-z][a-z0-9]*)$',$this->name,$result))
            return false;
        return $result[1];
    }/**/

    /**
    * returns service part of methodname (before the point='.')
    * @access public
    * @return string|false
    */
    function getServiceName() {
        if (!eregi('^([a-z][a-z0-9]*)\.',$this->name,$result))
            return false;
        return $result[1];
    }/**/

    /**
    * return call's domXmlRpcParams instance
    * @param
    * @access public
    * @return domXmlRpcParams
    */
    function & getParams() {
        return $this->params;
    }/**/

    /**
    * return content as xml
    * @param
    * @access public
    * @return string
    */
    function getXml() {
        // get own, root node
        if (!$rootNode=&$this->getNode())
            return false;
        // append name
        $nameNode=$rootNode->makeChild('methodName');
        $nameNode->setContent($this->name);
        // append params
        $this->params->appendToNode($rootNode);
        // append content
        //if (!$this->content)
            //return false;
        //$this->content->appendToNode($rootNode);
        // build and return xml
        $doc=$rootNode->getDomDoc();
        return $doc->dump_mem(1);
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var type desc
    */
    var $name; // methodName
    var $params; // ooXmlRpcParams instance
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
/**
    * build and return ooXmlRpcNode for content dom manipulation
    * @param
    * @access protected
    * @return ooxmlrpcnode
    */
    function & getNode() {
        $node=false;
        $baseXml='<?xml version="1.0" encoding="utf-8"?><methodCall />';
        if (!$dom=&domxml_open_mem($baseXml))
            return $node;
        $doc=&new ooXmlRpcDomNode($dom);
        $roots=&$doc->getxpathnodes('/*');
        $node=$roots[0];

        return $node;
    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcMethodResponse {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    * /
    function ooXmlRpcMethodResponse() {
        $this->__construct();
    }/**/

    /**
    *
    * @access
    * /
    function __construct() {

    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * sets content :
    * @param ooxmlrpcparams|ooxmlrpcfault &$content
    * @access public
    * @return bool
    */
    function setContent(&$content) {
        // check input
        if (!is_a($content,'ooxmlrpcparams') and !is_a($content,'ooxmlrpcfault'))
            return false;
        $this->content=&$content;
        return true;
    }/**/

    /**
    * build and return xml
    * @param
    * @access public
    * @return string
    */
    function getXml() {
        // get own, root node
        if (!$rootNode=&$this->getNode())
            return false;
        // append content
        if ($this->content)
            $this->content->appendToNode($rootNode);
        // build and return xml
        $doc=$rootNode->getDomDoc();
        return $doc->dump_mem(1);
    }/**/

    /**
    * Return values as an array, if content is set as ooXmlRpcParams
    * @access public
    * @return array
    */
    function getValues() {
        if (!is_a($this->content,'ooxmlrpcparams'))
            return false;
        return $this->content->getvalues();
    }/**/

    /**
    * Sets from xml string
    * @param string $xml
    * @access public
    * @return bool
    */
    function setFromXml($xml) {
        // creating domDocument instance
        if (!$domDocument=&domxml_open_mem($xml)) {
            //trigger_error("ooXmlRpcMethodResponse : invalid xml ");
            // echo htmlentities($xml);
            return false;
        }
        // building
        $responseNode=new ooXmlRpcDomNode($domDocument);
        //$deb=&wak::getservice('wakdebug');
        // params or fault ?
        if ($nodes=$responseNode->getXpathNodes('/methodResponse/fault')) {
            // fault
            $this->content=new ooXmlRpcFault();
            $this->content->setFromNode($nodes[0]);
        } else if ($nodes=$responseNode->getxpathNodes('/methodResponse/params')) {
            // params
            $this->content=ooXmlRpcParams();
            //$pNode=&new ooXmlRpcDomNode($nodes[0]);
            $this->content->setFromNode($nodes[0]);
        } else {
            // empty response ???
            return true;
            //return false;
        }
        //$this->params=&new domXmlRpcParams();
        return true;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var type desc
    */
    var $content; // ooXmlRpcMethodParams|ooXmlRpcMethodFault

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    * build and return ooXmlRpcNode for content dom manipulation
    * @param
    * @access protected
    * @return ooxmlrpcnode
    */
    function & getNode() {
        $node=false;
        $baseXml='<?xml version="1.0" encoding="utf-8"?><methodResponse />';
        if (!$dom=&domxml_open_mem($baseXml))
            return $node;
        $doc=&new ooXmlRpcDomNode($dom);
        $roots=&$doc->getxpathnodes('/*');
        $node=$roots[0];

        return $node;
    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcParams {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    * /
    function ooXmlRpcParams() {
        $this->__construct();
    }/**/

    /**
    *
    * @access
    * /
    function __construct() {

    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * sets value(s) from node
    * @param ooXmlRpcNode &$node
    * @access public
    * @return bool
    */
    function setFromNode(&$node) {
        if (!is_a($node,'ooxmlrpcdomnode')) {
            return false;
        }
        //var_dump($node);
        // searching for values
        if ($vNodes=$node->getxpathnodes('child::param/value')) {
            for ($n=0; $n<count($vNodes); $n++) {
                $value=&new ooXmlRpcValue();
                //$vNode=&new ooXmlRpcDomNode($valueNodes[$n]);
                $value->setFromNode($vNodes[$n]);
                $this->values[]=&$value;
            }
        }
        return true;
    }/**/

    /**
    * build and appends self node(s) to parent node
    * @param
    * @access public
    * @return bool
    */
    function appendToNode(&$parentNode) {
        // build & append self
        $selfNode=&$parentNode->makeChild('params');
        // build & append values to self
        for ($n=0; $n<count($this->values); $n++) {
            $paramNode=$selfNode->makeChild('param');
            $this->values[$n]->appendToNode($paramNode);
        }
        return true;
    }/**/

    /**
    * sets a param's value
    * @param ooXMlRpcValue|mixed $value either ooXmlRpcValue instance, or php var
    * @param int $index param index, if ommitted will be instered at end of array
    * @access public
    * @return bool
    */
    function setParam($value,$index=null) {
        // building value instance if necessary
        if (!is_a($value,'ooXmlRpcValue')) {
            $valueO=new ooXmlRpcValue();
            if (!$valueO->setValue($value))
                return false;
            $value=&$valueO;
        }
        if (is_int($index))
            $this->values[$index]=$value;
        else
            $this->values[]=&$value;
        return true;
    }/**/

    /**
    * return values as an array of php vars
    * @access public
    * @return array
    */
    function & getValues() {
        $values=array();
        for ($n=0; $n<count($this->values); $n++)
            $values[]=$this->values[$n]->getValue();
        return $values;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return ooXmlRpcValue
    */
    function & getParam($index) {
        $r=false;
        if (isset($this->values[$index]))
            $r=$this->values[$index];
        return $r;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var type desc
    */
    var $values=array();

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}

/**
* handle for <fault> elements
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcFault{
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access public
    */
    function ooXmlRpcFault($code=NULL,$string=NULL) {
        $this->__construct($code,$string);
    }/**/

    /**
    *
    * @access public
    */
    function __construct($code=NULL,$string=NULL) {
        if ($code)
            $this->setCode($code);
        if ($string)
            $this->setString($string);
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * sets faultCode
    * @param int $code
    * @access public
    * @return bool
    */
    function setCode($code) {
        if (!is_int($code))
            return false;
        $this->code=$code;
        return true;
    }/**/

    /**
    * sets faultString
    * @param string $string
    * @access public
    * @return bool
    */
    function setString($string) {
        if (!is_string($string))
            return false;
        $this->string=$string;
        return true;
    }/**/

    /**
    * build self node and append it to parent node
    * @param ooXmlRpcDomNode &$node
    * @access public
    * @return bool
    */
    function appendToNode(&$node) {
        if (!is_a($node,'ooxmlrpcdomnode'))
            return false;
        if (!$this->isValid())
            return false;
        // building struct value
        $data=array(
            'faultCode'=>$this->code
            ,'faultString'=>$this->string
        );
        $struct=&new ooXmlRpcValue('struct',$data);
        // appending self <fault>
        $faultNode=&$node->makeChild('fault');
        // appending struct
        $struct->appendToNode($faultNode);
        return true;
    }/**/

    /**
    * check/tells if couple code/string is valid (upon types only)
    * @param
    * @access public
    * @return bool
    */
    function isValid() {
        return (is_int($this->code) and is_string($this->string));
    }/**/

    /**
    * sets content from node
    * @param ooXmlRpcDomNode & $node
    * @access public
    * @return bool
    */
    function setFromNode(& $node) {
        if (!is_a($node,'ooXmlRpcDomNode'))
            return false;
        // searching code
        if ($nodes=$node->getxpathnodes('child::value/struct/member[name=\'faultCode\']/value')) {
            $value=new ooxmlrpcvalue();
            $value->setfromnode($nodes[0]);
            $this->code=$value->getvalue();
        }
        // searching string
        if ($nodes=$node->getxpathnodes('child::value/struct/member[name=\'faultString\']/value')) {
            $value=new ooxmlrpcvalue();
            $value->setfromnode($nodes[0]);
            $this->string=$value->getvalue();
        }
        // echo wakaffvar($this);
        return true;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var int faultCode
    */
    var $code;
    /**
    * @var string faultString
    */
    var $string='';

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcValue {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    */
    function ooXmlRpcValue($type=null,$value=null) {
        $this->__construct($type,$value);
    }/**/

    /**
    * "real" constructor. Sets type and value if provided
    * @param string $type
    * @param mixed $value
    * @access public
    */
    function __construct($type=null,$value=null) {
        $this->setType($type);
        $this->setValue($value);
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * sets value from ooXmlRpcNode instance
    * @param ooxmlrpcnode &$node
    * @access public
    * @return bool
    */
    function setFromNode(&$node) {
        // checking input
        if (!is_a($node,'ooxmlrpcdomnode')) {
            return false;
        }
        //var_dump($node);
        // searching for type = value child element tagname
        if ($typeNodes=$node->getxpathnodes('child::*')) {
            $type=$typeNodes[0]->getTagName();
            $valueXpath='child::*';
            //$valueNodes=$node->getXpathNodes('child::*');
        } else {
            // no child element, default to string
            $type='string';
            $valueXpath='text()';
            //$valueNodes=$node->getXpathNodes('text()');
        }
        $this->type=$type;
        // getting value content node(s)
        $valueNodes=$node->getXpathNodes($valueXpath);
        // extracting value, method depends on type
        switch($type) {
            // non-scalars
            case 'array':
                $this->value=array();
                // sub-values are within <data>
                $valueNodes=$valueNodes[0]->getXpathNodes('data/*');
                // cycling values
                for ($n=0; $n<count($valueNodes); $n++) {
                    // building instance
                    $value=&new ooXmlRpcValue();
                    // setting from node
                    $value->setFromNode($valueNodes[$n]);
                    // importing instance
                    $this->value[]=&$value;
                }
                break;
            case 'struct':
                $this->value=array();
                // sub-values are within <data>
                $valueNodes=$valueNodes[0]->getXpathNodes('member');
                // cycling values (<member> nodes)
                for ($n=0; $n<count($valueNodes); $n++) {
                    // building instance
                    $value=&new ooXmlRpcValue();
                    // getting key
                    $subKeyNodes=$valueNodes[$n]->getXpathNodes('name');
                    $key=$subKeyNodes[0]->getContent();
                    // getting value
                    $subValueNodes=$valueNodes[$n]->getXpathNodes('value');
                    // setting from node
                    $value->setFromNode($subValueNodes[0]);
                    // importing instance
                    $this->value[$key]=&$value;
                }
                break;
            // scalars
            case 'boolean':
                $value=$valueNodes[0]->getContent();
                if ($value=='1' or strtolower($value)=='true')
                    $this->value=true;
                else $this->value=false;
                break;
            case 'i4':
            case 'int':
            case 'integer':
                $value=$valueNodes[0]->getContent();
                $this->type='int';
                $this->value=intval($value);
                break;
            case 'double':
                $value=$valueNodes[0]->getContent();
                //$this->type='int';
                settype($value,'double');
                $this->value=&$value;
                break;
            case 'string':
                //$value=$valueNodes[0]->getContent();
                //$this->value=&$value;
                //break;
            case 'base64':
                //$value=$valueNodes[0]->getContent();
                //$this->value=base64_decode($value);
                //$this->value=&$value;
                //break;
            case 'dateTime.iso8601':
                //$value=$valueNodes[0]->getContent();
                //break;
            default: // scalar
                $this->value=$valueNodes[0]->getContent();
        }
        return true;
        //var_dump($valueNodes);
    }/**/

    /**
    * check/tells if the value is valid
    * @param mixed $value optional, checks current value if omitted
    * @access public
    * @return bool
    */
    function isValid($value=null) {
        if (is_null($value))
            $value=&$this->value;
        switch($this->type) {
            // scalars
            case 'boolean':
                return in_array($value,array(0,1));
            case 'string':
                return is_string($value);
            case 'int':
                return is_int($value);
            case 'double':
                return is_double($value);
            case 'base64':
                return ereg('[a-zA-Z0-9+=/]+',$value);
            case 'dateTime.iso8601':
                return ereg('[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}',$value);
            // non-scalars
            case 'array':
                return is_array($value);
            case 'struct':
                return is_array($value) or is_object($value);
            default:
                return false;
        }
    }/**/

    /**
    * check/tells if value is a scalar
    * @param mixed $value optional, checks current value if not provided
    * @access public
    * @return
    */
    function isScalar($value=null) {
        if (is_null($value))
            $value=&$this->value;
        return is_scalar($value);
    }/**/

    /**
    * return value's type
    * @access public
    * @return string int|string|boolean|base64|double|dateTime.iso8601|struct|array
    */
    function getType() {
        return $this->type;
    }/**/

    /**
    * sets type (value has to be null)
    * @param string $type
    * @access public
    * @return bool
    */
    function setType($type) {
        if (!in_array($type,array('string','int','double','boolean','base64','dateTime.iso8601','struct','array'))) {
            return false;
        }
        if (!is_null($this->value)) {
            return false;
        }
        $this->type=$type;
        return true;
    }/**/

    /**
    * sets value (type has to be set first, else it will be guessed
    * @param mixed $value
    * @access public
    * @return bool
    */
    function setValue($value) {
        if (is_null($value))
            return false;
        // if type is not set, try guessing
        if (is_null($this->type) and $guessed=$this->guessType($value)) {
            // echo "<p>guessed $guessed</p>";
            $this->setType($guessed);
        }
        if (!$this->isValid($value))
            return false;
        // scalar
        // $this->value=&$value;
        switch ($this->type) {
            // non-scalars
            case 'array':
            case 'struct':
                if (is_object($value))
                    $value=get_object_vars($value);
                if (!is_array($value))
                    return false;
                foreach ($value as $key=>$item) {
                    if (is_a($item,'ooxmlrpcvalue')) {
                        $this->value[$key]=$item;
                    } else {
                        $subValue=&new ooXmlRpcValue(null,$item);
                        if ($subValue->setValue($item))
                            $this->value[$key]=&$subValue;
                    }
                }
                break;
            // scalars
            case 'boolean':
                if ($value) $this->value=1;
                else $this->value=0;
                break;
            case 'int':
            case 'double':
            case 'base64':
            case 'string':
            case 'dateTime.iso8601':
                $this->value=&$value;
                break;
            default:
                return false;
        }

        return true;
    }/**/

    /**
    * build and append self node(s) to parent node
    * @param ooXmlRpcDomNode &$parentNode
    * @access public
    * @return bool
    */
    function appendToNode(& $parentNode) {
        // build & append value node
        $valueNode=&$parentNode->makeChild('value');
        // build & append type node to value node
        $typeNode=&$valueNode->makeChild($this->type);
        // insert content into type node
        switch ($this->type) {
            // non-scalars
            case 'array':
                //$arrayNode=&$typeNode->makeChild('array');
                // append data node
                $dataNode=&$typeNode->makeChild('data');
                // append child values to data node
                for ($n=0; $n<count($this->value); $n++) {
                    $this->value[$n]->appendToNode($dataNode);
                }
                break;
            case 'struct':
                // cycling through sub-value nodes
                if (!is_array($this->value)) return false;
                foreach ($this->value as $key=>$value) {
                    // appending <member> nodes
                    $memberNode=&$typeNode->makeChild('member');
                    // appending <name>
                    $nameNode=&$memberNode->makeChild('name');
                    $nameNode->setContent($key);
                    // appending <value>
                    //$deb=&wak::getservice('wakdebug');
                    //echo $deb->vardump($value);
                    $value->appendToNode($memberNode);
                }
                break;
            //scalars
            default:
                $typeNode->setContent($this->value);
        }
        return true;
    }/**/

    /**
    * tries to guess a xml-rpc type for the var
    * @param
    * @access public
    * @return
    */
    function guessType(&$var) {
        if (is_bool($var))
            return 'boolean';
        if (is_int($var))
            return 'int';
        if (is_double($var))
            return 'double';
        if (is_object($var))
            return 'struct';
        if (is_array($var)) {
            if (is_int(key($var)))
                return 'array';
            else return 'struct';
        }
        if (is_string($var) and ereg('[0-9]{6}T[0-9]{2}:[0-9]{2}:[0-9]{2}',$var))
            return 'dateTime.iso8601';
        if (is_string($var) and ereg('^[a-zA-Z0-9+/=]{50,}$',$var))
            return 'base64';
        if (is_string($var))
            return 'string';
        
        return false;
    }/**/

    /**
    * Return value
    * @access public
    * @return mixed
    */
    function getValue() {
        if (!is_array($this->value))
                return $this->value;
        // recursion to get sub-values
        $value=array();
        foreach (array_keys($this->value) as $key) {
            $value[$key]=$this->value[$key]->getValue();
        }
        return $value;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var type desc
    */
    var $type; // int|boolean|string|base64|dateTime.iso8601|struct|array
    var $value;

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/

    /**
    *
    * @param
    * @access private
    * @return
    * /
    function () {

    }/**/
}
?>
